//文件上传的状态值
export enum statusCode {
  //待上传
  upload = 0,
  //上传中
  uploading = 1,
  //上传失败
  fail = 2,
  //上传成功
  success = 3,
  //超过大小限制
  max = 4,
}
//文件对象结构
export interface file {
  url: string //当前显示的图片地址，这是个本地临时地址。待上传前，成功后会替换服务器地址。
  status?: string //上传状态文本
  progress?: number //当前文件上传的进度
  uid?: string | number //文件唯一标识id
  statusCode?: statusCode //文件状态
  response?: any //上传成功后的回调数据。
  name?: string //文件名称
  [propName: string]: any
}

//上传class对象的配置。
export interface fileConfig {
  maxSize?: number //每一个文件上传的最大尺寸，默认为10mb
  maxFile?: number //一次选择文件最大数量。
  fileType?: Array<string> //文件选择的类型。
  hostUrl?: string //上传文件的服务器地址
  fileList?: Array<file> //已上传的文件列表。
  autoUpload?: boolean
  header?: Object //头部参数。
  formData?: Object //额外的表单数据。
  formName?: string
  statusCode?: number //服务返回成功的状态码标志，默认200表示成功。
}
export function getUid(length = 3) {
  return Number(Number(Math.random().toString().substr(3, length) + Date.now()).toString(8))
}
/**
 * 上传文件。
 * 作者：tmzdy
 * 时间：2022年4月27日
 * 联系：zhongjihan@sina.com
 * @method {Function} beforeChooesefile -- 选择图片上传前执行的勾子。
 * @method {Function} chooesefile -- 选择图片上传，弹出层进行选择文件 。
 * @method {Function} chooesefileAfter -- 选择图片、视频文件成功后触发。返回选择后的文件。此时还未加入待上传列表，会返回 一个文件列表用于过滤。需要在函数中再返回 一个文件 列表，
 * @method {Function} chooesefileSuccess -- 文件已经加入到了待上传文件列表,需要返回过滤的文件列表
 * @method {Function} beforeAddfile -- 动态加入预计加入文件前执行的勾子，返回true才会正式加入到待上传列表中。
 * @method {Function} addFile -- 动态加入预上传的文件。
 * @method {Function} progress -- 进度。
 * @method {Function} fail -- 失败。服务器出现非200时触发。
 * @method {Function} beforeSuccess -- 服务器返回成功，立即执行的勾子。如果此时返回false,则表示文件 上传失败。
 * @method {Function} success -- 成功。
 * @method {Function} complete -- 单个文件完成。不管上传成功与失败否，都会触发此函数。
 * @method {Function} uploadComplete -- 所有文件上传完时触发
 * @method {Function} beforeStart -- 开始上传。前的校验勾子。如果返回false,将阻止上传
 * @method {Function} start -- 开始上传。
 * @method {Function} stop -- 停止上传。中止上传时触发的函数。
 */
export class uploadfile {
  //文件列表。
  filelist: Array<file> = []
  isStop = false
  index = 0
  config: fileConfig = {}
  uploadobj: UniNamespace.UploadTask | null = null
  constructor(config: fileConfig) {
    let cf: fileConfig = {
      maxSize: 10 * 1024 * 1024,
      maxFile: 9,
      fileType: ['album', 'camera'],
      fileList: [],
      autoUpload: true,
      header: {},
      formData: {},
      formName: 'file',
    }
    cf = { ...cf, ...(arguments[0] ?? {}) }
    //配置{name: 'file', // 上传时的文件key名。默认file,header: {}, // 上传的头部参数。}
    this.config = cf
    this.addFile(cf.fileList)
    delete this.config.fileList
  }
  async beforeChooesefile() {
    return true
  }
  async chooesefileAfter(fileList: Array<file>) {
    return fileList
  }
  async chooesefileSuccess(fileList: Array<file>) {
    return fileList
  }
  delete(item: file) {
    const index = this.filelist.findIndex((el) => el.uid == item.uid)
    if (index > -1) {
      const p = [...this.filelist]
      p.splice(index, 1)
      this.filelist = [...p]
    }

    return this.filelist
  }
  async clear() {
    /** 清清前要选暂停所有正在上传的文件 */
    this.stop()
    this.filelist = []
  }
  setFileStatus(item: file) {
    const index = this.filelist.findIndex((el) => el.uid == item.uid)
    if (index > -1) {
      const p = [...this.filelist]
      p.splice(index, 1, item)
      this.filelist = [...p]
    }
  }
  /**
   * 成功后返回选择后的图片列表。
   */
  async chooesefile(): Promise<Array<file>> {
    const t = this

    return new Promise(async (rs, rj) => {
      const isready = await t.beforeChooesefile()
      if (!isready) {
        rs([])
        return
      }
      uni.chooseImage({
        count: t.config.maxFile,
        sourceType: t.config.fileType,
        fail: (e) => {
          rj('取消选择')
        },
        success: async (res) => {
          if (res.tempFilePaths.length == 0) {
            rj('未选择')
            return
          }

          const imgarray = res.tempFilePaths
          const fielist = res.tempFiles
          const jgsk: Array<file> = []
          //0待上传，1上传中，2上传失败，3上传成功。4超过大小限制
          imgarray.forEach((item: string, index: number) => {
            const isMaxsize = fielist[index].size > t.config.maxSize ? true : false
            jgsk.push({
              url: item,
              status: isMaxsize ? '超过大小' : '待上传',
              progress: isMaxsize ? 100 : 0,
              uid: getUid(),
              statusCode: isMaxsize ? statusCode.max : statusCode.upload,
              response: null,
              name: fielist[index].name ?? '',
            })
          })

          const isreadyChoose = await t.chooesefileAfter(jgsk)

          if (!Array.isArray(isreadyChoose) || typeof isreadyChoose != 'object') {
            rj('chooesefileAfter:函数过滤，没有返回文件列表。')
            return
          }

          t.filelist.push(...isreadyChoose)

          t.chooesefileSuccess(isreadyChoose)
          rs(isreadyChoose)
          if (t.config.autoUpload) {
            setTimeout(function () {
              t.start()
            }, 500)
          }
        },
      })
    })
  }
  async chooseMPH5weixinFile() {
    const t = this
    return new Promise((rs, rj) => {
      let fs = uni.chooseFile
      // #ifdef MP-WEIXIN || MP-QQ
      fs = uni.chooseMessageFile
      // #endif
      const config = {
        count: t.config.maxfile,
        type: t.config.type,
        extension: t.config.extension,
      }
      if (!t.config.extension || !Array.isArray(t.config.extension) || t.config.extension?.length == 0) {
        delete config.extension
      }
      fs({
        ...config,
        fail: (e) => {
          console.error(e)
          uni.$tm.toast('已取消选择')
          rj(e)
        },
        success: (res) => {
          if (res.tempFiles.length == 0) {
            uni.$tm.toast('未选择')
            return
          }
          const fielist = res.tempFiles
          const jgsk = []
          //0待上传，1上传中，2上传失败，3上传成功。4超过大小限制
          fielist.forEach((item, index) => {
            const isMaxsize = fielist[index].size > t.config.maxsize ? true : false
            let ftype = item.name || ''
            if (ftype) {
              ftype = ftype.substr(ftype.lastIndexOf('.') + 1).toLocaleLowerCase()
            }
            jgsk.push({
              url: item.path,
              name: item.name || '默认文件名称',
              type: ftype,
              status: isMaxsize ? '超过大小' : '待上传',
              progress: isMaxsize ? 100 : 0,
              fileId: guid(),
              statusCode: isMaxsize ? 4 : 0,
              data: null, //上传成功后的回调数据。
            })
          })
          t.filelist.push(...jgsk)

          t.selected(t.filelist)
          if (t.config.isAuto) {
            t.start()
          }

          rs(t.filelist)
        },
      })
    })
  }
  setConfig(config: fileConfig) {
    this.config = { ...this.config, ...(config ?? {}) }
  }
  /**
   * 动态加入文件
   * @param {Object} filelist
   */
  addFile(filelist: Array<file> = []) {
    if (typeof filelist !== 'object' && !Array.isArray(filelist)) return
    const total_uid = new Set(this.filelist.map((e) => e.uid))
    const total_url = new Set(this.filelist.map((e) => e.url))
    const cfilelist = filelist.map((el) => {
      return {
        ...el,
        status: el?.status ?? '待上传',
        statusCode: el?.statusCode ?? statusCode.upload,
        uid: el?.uid ?? getUid(),
        progress: el?.progress ?? 0,
        name: el?.name ?? '',
        response: el?.response ?? null,
        url: el?.url ?? '',
      }
    })
    const filterFIle = cfilelist.filter((item) => !total_uid.has(item.uid) && !total_url.has(item.url))
    this.filelist.push(...filterFIle)
  }
  beforeSuccess(item: file) {
    return Promise.resolve(true)
  }
  beforeStart(item: file) {
    return Promise.resolve(true)
  }
  // 进度。
  progress(item: file, index: number) {}
  // 失败
  fail(item: file) {}
  // 成功
  success(item: file, fileList: Array<file>) {}
  // 完成。
  complete(filelist: file) {}
  uploadComplete(filelist: Array<file>) {}
  awaitTime() {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(true)
      }, 20)
    })
  }
  // 开始上传。
  async start() {
    if (this.filelist.length <= 0) {
      console.error('未选择图片,已取消上传')
      return
    }
    const t = this
    // t重新开始上传从头开始。
    this.index = 0
    this.isStop = false
    async function startupload() {
      if (t.isStop) return

      const item = t.filelist[t.index]

      if (!item || typeof item === 'undefined') {
        // 文件不存在。直接结束。
        t.uploadComplete(t.filelist)
        return
      }

      const canbleStart = await t.beforeStart(item)
      if (!canbleStart) {
        item.statusCode = statusCode.fail
        item.status = '不允许上传'
        t.filelist.splice(t.index, 1, item)
        t.index++
        t.setFileStatus(item)
        t.fail(item)
        t.complete(item)
        startupload()
        return
      }

      if (item.statusCode == 3 || item.statusCode == 1 || item.statusCode == 4 || item.statusCode == 2) {
        // 直接跳过。至下一个文件。
        t.index++
        startupload()
        return
      }

      item.statusCode = statusCode.uploading
      item.status = '上传中...'
      t.setFileStatus(item)
      const upObj = (t.uploadobj = uni.uploadFile({
        url: String(t.config.hostUrl),
        name: t.config?.formName ?? 'file',
        header: t.config?.header ?? {},
        filePath: item.url,
        formData: { name: item.name, ...t.config.formData },
        success: async (res) => {
          if (t.isStop) return
          item.response = res.data
          const isOksuccess = await t.beforeSuccess(item)
          const statusCode_reonese = t.config?.statusCode ?? 200
          if (res.statusCode != statusCode_reonese || !isOksuccess) {
            item.statusCode = statusCode.fail
            item.status = '上传失败'
            t.fail(item)
            t.setFileStatus(item)
            t.index++
            return
          }

          // 上传成功。
          item.statusCode = statusCode.success
          item.status = '上传成功'
          t.setFileStatus(item)
          t.success(item, t.filelist)
          t.index++
        },
        fail: (res) => {
          if (t.isStop) return
          item.statusCode = statusCode.fail
          item.status = '上传失败'
          t.setFileStatus(item)
          t.fail(item)
          t.index++
        },
        complete: async (res) => {
          if (t.isStop) return
          await t.awaitTime()
          t.complete(item)
          // 直接下一个文件。
          startupload()
        },
      }))
      if (upObj) {
        const item = t.filelist[t.index]
        upObj.onProgressUpdate(async (res) => {
          if (t.isStop) return
          item.progress = res.progress
          item.statusCode = statusCode.uploading
          item.status = '...'
          t.setFileStatus(item)
          t.progress(item, t.index)
        })
      }
    }
    await startupload()
  }
  // 停止上传
  stop() {
    this.isStop = true
    if (this.uploadobj != null) {
      this.uploadobj.abort()
    }
  }
}
